其他
函数式编程的Java编码实践:利用惰性写出高性能且抽象的代码
一 抽象一定会导致代码性能降低?
public class User {
// 用户 id
private Long uid;
// 用户的部门,为了保持示例简单,这里就用普通的字符串
// 需要远程调用 通讯录系统 获得
private String department;
// 用户的主管,为了保持示例简单,这里就用一个 id 表示
// 需要远程调用 通讯录系统 获得
private Long supervisor;
// 用户所持有的权限
// 需要远程调用 权限系统 获得
private Set<String> permission;
}
public boolean isSupervisor(User u1, User u2) {
return Objects.equals(u1.getSupervisor(), u2.getUid());
}
业务建模只需要考虑贴合业务,而不需要考虑底层的性能问题,真正实现业务层和物理层的解耦
业务逻辑与外部调用分离,无论外部接口如何变化,我们总是有一层适配层保证核心逻辑的稳定
业务逻辑看起来就是纯粹的实体操作,易于编写单元测试,保障核心逻辑的正确性
二 严格与惰性:Java 8 的 Supplier 的本质
Supplier<Integer> a = () -> 10 + 1;
int b = a.get() + 1;
三 Supplier 的进一步优化:Lazy
/**
* 为了方便与标准的 Java 函数式接口交互,Lazy 也实现了 Supplier
*/
public class Lazy<T> implements Supplier<T> {
private final Supplier<? extends T> supplier;
// 利用 value 属性缓存 supplier 计算后的值
private T value;
private Lazy(Supplier<? extends T> supplier) {
this.supplier = supplier;
}
public static <T> Lazy<T> of(Supplier<? extends T> supplier) {
return new Lazy<>(supplier);
}
public T get() {
if (value == null) {
T newValue = supplier.get();
if (newValue == null) {
throw new IllegalStateException("Lazy value can not be null!");
}
value = newValue;
}
return value;
}
}
Lazy<Integer> a = Lazy.of(() -> 10 + 1);
int b = a.get() + 1;
// get 不会再重新计算, 直接用缓存的值
int c = a.get();
public class User {
// 用户 id
private Long uid;
// 用户的部门,为了保持示例简单,这里就用普通的字符串
// 需要远程调用 通讯录系统 获得
private Lazy<String> department;
// 用户的主管,为了保持示例简单,这里就用一个 id 表示
// 需要远程调用 通讯录系统 获得
private Lazy<Long> supervisor;
// 用户所含有的权限
// 需要远程调用 权限系统 获得
private Lazy<Set<String>> permission;
public Long getUid() {
return uid;
}
public void setUid(Long uid) {
this.uid = uid;
}
public String getDepartment() {
return department.get();
}
/**
* 因为 department 是一个惰性加载的属性,所以 set 方法必须传入计算函数,而不是具体值
*/
public void setDepartment(Lazy<String> department) {
this.department = department;
}
// ... 后面类似的省略
}
Long uid = 1L;
User user = new User();
user.setUid(uid);
// departmentService 是一个rpc调用
user.setDepartment(Lazy.of(() -> departmentService.getDepartment(uid)));
// ....
String department = departmentService.getDepartment(uid);
Long supervisor = SupervisorService.getSupervisor(department);
四 Lazy 实现函子(Functor)
1 函子的计算对象
2 函子的定义
在盒子中装的是类型,而不是 1 和 "1" 的原因是,盒子中不一定是单个值,比如集合,甚至是更加复杂的多值映射关系。
// 反例,不能成为函子,因为这个方法没有在盒子中如实反映 function 的映射关系
public Box<S> map(Function<T,S> function) {
return new Box<>(null);
}
单位元律:Box<T> 在应用了恒等函数后,值不会改变,即 box.equals(box.map(Function.identity()))始终成立(这里的 equals 只是想表达的一个数学上相等的含义)
复合律:假设有两个函数 f1 和 f2,map(x -> f2(f1(x))) 和 map(f1).map(f2) 始终等价
3 Lazy 函子
public <S> Lazy<S> map(Function<? super T, ? extends S> function) {
return Lazy.of(() -> function.apply(get()));
}
Lazy<String> departmentLazy = Lazy.of(() -> departmentService.getDepartment(uid));
Lazy<Long> supervisorLazy = departmentLazy.map(
department -> SupervisorService.getSupervisor(department)
);
4 遇到了更加棘手的情况
Lazy<Lazy<Set<String>>> permissions = departmentLazy.map(department ->
supervisorLazy.map(supervisor -> getPermissions(department, supervisor))
);
Lazy<Long> param1Lazy = Lazy.of(() -> 2L);
Lazy<Long> param2Lazy = Lazy.of(() -> 2L);
Lazy<Long> param3Lazy = Lazy.of(() -> 2L);
Lazy<Lazy<Lazy<Long>>> result = param1Lazy.map(param1 ->
param2Lazy.map(param2 ->
param3Lazy.map(param3 -> param1 + param2 + param3)
)
);
五 Lazy 实现单子 (Monad)
1 单子的定义
2 Lazy 单子
public <S> Lazy<S> flatMap(Function<? super T, Lazy<? extends S>> function) {
return Lazy.of(() -> function.apply(get()).get());
}
Lazy<Set<String>> permissions = departmentLazy.flatMap(department ->
supervisorLazy.map(supervisor -> getPermissions(department, supervisor))
);
Lazy<Long> param1Lazy = Lazy.of(() -> 2L);
Lazy<Long> param2Lazy = Lazy.of(() -> 2L);
Lazy<Long> param3Lazy = Lazy.of(() -> 2L);
Lazy<Long> result = param1Lazy.flatMap(param1 ->
param2Lazy.flatMap(param2 ->
param3Lazy.map(param3 -> param1 + param2 + param3)
)
);
3 题外话:函数式语言中的单子语法糖
do
param1 <- param1Lazy
param2 <- param2Lazy
param3 <- param3Lazy
-- 注释: do 记法中 return 的含义和 Java 完全不一样
-- 它表示将值打包进盒子里,
-- 等价的 Java 写法是 Lazy.of(() -> param1 + param2 + param3)
return param1 + param2 + param3
六 Lazy 的最终代码
public class Lazy<T> implements Supplier<T> {
private final Supplier<? extends T> supplier;
private T value;
private Lazy(Supplier<? extends T> supplier) {
this.supplier = supplier;
}
public static <T> Lazy<T> of(Supplier<? extends T> supplier) {
return new Lazy<>(supplier);
}
public T get() {
if (value == null) {
T newValue = supplier.get();
if (newValue == null) {
throw new IllegalStateException("Lazy value can not be null!");
}
value = newValue;
}
return value;
}
public <S> Lazy<S> map(Function<? super T, ? extends S> function) {
return Lazy.of(() -> function.apply(get()));
}
public <S> Lazy<S> flatMap(Function<? super T, Lazy<? extends S>> function) {
return Lazy.of(() -> function.apply(get()).get());
}
}
七 构造一个能够自动优化性能的实体
@Component
public class UserFactory {
// 部门服务, rpc 接口
@Resource
private DepartmentService departmentService;
// 主管服务, rpc 接口
@Resource
private SupervisorService supervisorService;
// 权限服务, rpc 接口
@Resource
private PermissionService permissionService;
public User buildUser(long uid) {
Lazy<String> departmentLazy = Lazy.of(() -> departmentService.getDepartment(uid));
// 通过部门获得主管
// department -> supervisor
Lazy<Long> supervisorLazy = departmentLazy.map(
department -> SupervisorService.getSupervisor(department)
);
// 通过部门和主管获得权限
// department, supervisor -> permission
Lazy<Set<String>> permissionsLazy = departmentLazy.flatMap(department ->
supervisorLazy.map(
supervisor -> permissionService.getPermissions(department, supervisor)
)
);
User user = new User();
user.setUid(uid);
user.setDepartment(departmentLazy);
user.setSupervisor(supervisorLazy);
user.setPermissions(permissionsLazy);
}
}
八 异常处理
异常处理肯定不能交给业务逻辑,这样会影响业务逻辑的纯粹性,让我们前功尽弃。比较理想的方式是交给惰性值的加载逻辑 Supplier。在 Supllier 的计算逻辑中就充分考虑各种异常情况,重试或者抛出异常。虽然抛出异常可能不是那么“函数式”,但是比较贴近 Java 的编程习惯,而且在关键的值获取不到时就应该通过异常阻断业务逻辑的运行。
九 总结
十 题外话:Java 中缺失的柯里化与应用函子(Applicative)
// 注意,这里的 function 是装在 lazy 里面的
public <S> Lazy<S> apply(Lazy<Function<? super T, ? extends S>> function) {
return Lazy.of(() -> function.get().apply(get()));
}
-- 注释: 结果为 box c
box f <*> box a <*> box b
参考资料
在 Java 函数式类库 VAVR 中提供了类似的 Lazy 实现,不过如果只是为了用这个一个类的话,引入整个库还是有些重,可以利用本文的思路直接自己实现
函数式编程进阶:应用函子 前端角度的函数式编程文章,本文一定程度上参考了里面盒子的类比方法:https://juejin.cn/post/6891820537736069134?spm=ata.21736010.0.0.595242a7a98f3U
《Haskell函数式编程基础》
《Java函数式编程》
程序员是要专精,还是要广度?
点击阅读原文查看详情!